package gqz.testdemo.DrawView;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Xfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewGroup;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2018/9/3.
 */

public class DrawView2 extends View {

    final String TAG = "DrawView-->";

    private Context context;
    private Paint mPaint;
    private Path mPath, savePath;
    private float mLastX;
    private float mLastY;
    private Bitmap thumbnail;
    private Bitmap srcBmp, srcBg;
    private Bitmap mBufferBitmap;
    private Canvas mBufferCanvas;

    private static final int MAX_CACHE_STEP = 20;

    private List<DrawInfo> mDrawingList;
    private List<DrawInfo> mRemovedList;

    private Xfermode mClearMode;
    private float mDrawSize;
    private float mEraserSize;

    private boolean mCanEraser;

    private Callback mCallback;
    private ScaleGestureDetector detector;

    public enum Mode {
        DRAW,
        ERASER
    }

    private Mode mMode = Mode.DRAW;

    public DrawView2(Context context) {
        this(context, null);
    }

    public DrawView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        setDrawingCacheEnabled(true);
        buildDrawingCache();
        init();
        detector = new ScaleGestureDetector(context, scaleListener);
    }

    public interface Callback {
        void onUndoRedoStatusChanged();
    }

    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setFilterBitmap(true);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mDrawSize = 5;
        mEraserSize = 20;
        mPaint.setStrokeWidth(mDrawSize);
        mPaint.setColor(Color.BLACK);

        mClearMode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
    }

    private void initBuffer() {
        mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
        mBufferCanvas = new Canvas(mBufferBitmap);
        mBufferCanvas.drawColor(Color.WHITE);
        srcW = getWidth();
        srcH = getHeight();
//        srcBmp = Bitmap.createBitmap(mBufferBitmap);
    }

    public Mode getMode() {
        return mMode;
    }

    public void setMode(Mode mode) {
        if (mode != mMode) {
            mMode = mode;
            if (mMode == Mode.DRAW) {
                mPaint.setXfermode(null);
                mPaint.setStrokeWidth(mDrawSize);
            } else {
                mPaint.setXfermode(mClearMode);
                mPaint.setStrokeWidth(mEraserSize);
            }
        }
    }

    public void setEraserSize(float size) {
        mEraserSize = size;
    }

    public void setPenRawSize(float size) {
        mEraserSize = size;
    }

    public void setPenColor(int color) {
        mPaint.setColor(color);
    }

    public void setPenAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    public void setBg(Bitmap bmp) {
        srcBg = bmp;
    }

    public void setBg(Drawable drawable) {
        BitmapDrawable bd = (BitmapDrawable) drawable;
        setBg(bd.getBitmap());
    }

    private class DrawInfo {
        Paint paint;
        Path path;

        public void draw(Canvas canvas) {
//            Path scalePath = new Path(path);
//            scalePath
//            paint.setStrokeWidth(paint.getStrokeWidth() * curScaleRatio);
            canvas.drawPath(path, paint);
        }
    }

    public void reDraw() {
        if (mDrawingList != null) {
//            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            mBufferCanvas.drawColor(Color.WHITE);
//            mBufferCanvas.save();
            Matrix matrix = new Matrix();
            matrix.postScale(curScaleRatio, curScaleRatio);
            mBufferCanvas.setMatrix(matrix);
            drawBg();
            for (DrawInfo drawInfo : mDrawingList) {
                drawInfo.draw(mBufferCanvas);
            }
//            mBufferCanvas.restore();
            invalidate();
        }
    }

    public boolean canRedo() {
        return mRemovedList != null && mRemovedList.size() > 0;
    }

    public boolean canUndo() {
        return mDrawingList != null && mDrawingList.size() > 0;
    }

    public void redo() {
        int size = mRemovedList == null ? 0 : mRemovedList.size();
        if (size > 0) {
            DrawInfo info = mRemovedList.remove(size - 1);
            mDrawingList.add(info);
            mCanEraser = true;
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public void undo() {
        int size = mDrawingList == null ? 0 : mDrawingList.size();
        if (size > 0) {
            DrawInfo info = mDrawingList.remove(size - 1);
            if (mRemovedList == null) {
                mRemovedList = new ArrayList<>(MAX_CACHE_STEP);
            }
            if (size == 1) {
                mCanEraser = false;
            }
            mRemovedList.add(info);
            reDraw();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public void clear() {
        if (mBufferBitmap != null) {
            if (mDrawingList != null) {
                mDrawingList.clear();
            }
            if (mRemovedList != null) {
                mRemovedList.clear();
            }
            mCanEraser = false;
            mBufferBitmap.eraseColor(Color.TRANSPARENT);
            mBufferCanvas.drawColor(Color.WHITE);
            invalidate();
            if (mCallback != null) {
                mCallback.onUndoRedoStatusChanged();
            }
        }
    }

    public Bitmap buildBitmap() {
        Bitmap bm = getDrawingCache();
        Bitmap result = Bitmap.createBitmap(bm);
        destroyDrawingCache();
        return result;
    }

    private void saveDrawingPath() {
        if (mDrawingList == null) {
            mDrawingList = new ArrayList<>(MAX_CACHE_STEP);
        } else if (mDrawingList.size() == MAX_CACHE_STEP) {
            mDrawingList.remove(0);
        }
        Path cachePath = new Path(savePath);
        Paint cachePaint = new Paint(mPaint);
        DrawInfo info = new DrawInfo();
        info.path = cachePath;
        info.paint = cachePaint;
        mDrawingList.add(info);
        mCanEraser = true;
        if (mCallback != null) {
            mCallback.onUndoRedoStatusChanged();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBufferBitmap != null) {
            canvas.drawBitmap(mBufferBitmap, 0, 0, null);
        }
    }

    float curX = 0, curY = 0;
    float firstX = 0, firstY = 0;
    public static final int MODE_DRAG = 1;//拖拽
    //    public static final int MODE_ZOOM = 2;//缩放
    int mode = 0, lastMode = 0;
    int srcW = 0, srcH = 0;
    float minScaleRatio = 0.5f;//最小放大比率
    float maxScaleRatio = 1.5f;//最大放大比率
    float curScaleRatio = 1f;//当前缩放比率
    int distance = 0;
    int pointerCount = 0;
    boolean isZooming = false;

    public void setMode(int mode) {
        lastMode = this.mode;
        this.mode = mode;
    }

    public byte[] Bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray();
    }

    /**
     * 压缩位图
     *
     * @param ratio 0.1~1.0
     * @return
     */
    private Bitmap compressBitmap(Bitmap bmp, float ratio) {
//        Matrix matrix = new Matrix();
//        matrix.setScale(ratio, ratio);
//        Bitmap newBmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
        Bitmap newBmp = Bitmap.createScaledBitmap(bmp, 200, 200, true);
        Log.i(TAG, "compressBitmap: comperss bmp size w*h: " + newBmp.getWidth() + "*" + newBmp.getHeight() + " size:" + newBmp.getByteCount());
        return newBmp;
    }

    private static PaintFlagsDrawFilter pfd = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

    public synchronized Bitmap scaleImageCavans(Bitmap bm, int newWidth, int newHeight) {
        if (bm == null) {
            return null;
        }
        Canvas mCanvas = new Canvas();
        Paint mPaint = new Paint();
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        Bitmap newbm = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.RGB_565);
        mCanvas.setBitmap(newbm);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mCanvas.save();
        mCanvas.scale(scaleWidth, scaleHeight);
//        mCanvas.setDrawFilter(pfd);//提高清晰度，但是耗时
        mCanvas.drawBitmap(bm, 0, 0, null);//耗时3~45ms
        mCanvas.restore();
        return newbm;
    }

    private void getNewSizeView(int w, int h, boolean isNeedScaleCanvas, float ratio) {
        ViewGroup.LayoutParams params = this.getLayoutParams();
        params.width = w;
        params.height = h;
        this.setLayoutParams(params);
//        if (isNeedScaleCanvas) {
//        Matrix matrix = new Matrix();
//        matrix.postScale(ratio, ratio);
        Bitmap tempBmp = null;
        if (isNeedScaleCanvas) {
//            tempBmp = Bitmap.createScaledBitmap(thumbnail, w, h, true);//耗时20~150ms
            tempBmp = scaleImageCavans(thumbnail, w, h);
        }
//        mBufferBitmap = Bitmap.createBitmap(mBufferBitmap, 0, 0, mBufferBitmap.getWidth(), mBufferBitmap.getHeight(), matrix, true);
//            mBufferCanvas = new Canvas(mBufferBitmap);
////            mBufferCanvas.drawColor(Color.WHITE);
////            mBufferCanvas.drawBitmap(mBufferBitmap, 0, 0, mPaint);
//            reDraw();
//        } else {
        //以下代码耗时3~30ms
        mBufferBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
        mBufferCanvas = new Canvas(mBufferBitmap);
//        mBufferCanvas.drawColor(Color.WHITE);
        if (tempBmp != null && !tempBmp.isRecycled()) {
            mBufferCanvas.drawBitmap(tempBmp, 0, 0, mPaint);
            tempBmp.recycle();
        }
//            mBufferCanvas.drawBitmap(oldBmp, 0, 0, mPaint);
//            reDraw();
//        }
    }

    public void addSize(int w, int h) {
        srcW += w;
        srcH += h;
        getNewSizeView((int) (srcW * curScaleRatio), (int) (srcH * curScaleRatio), false, 0);
        reDraw();
    }

    private void scaleView(float ratio) {
//        Log.i(TAG, "scaleView: ratio: " + ratio);
        curScaleRatio = ratio;
        int newW = (int) (srcW * ratio);
        int newH = (int) (srcH * ratio);
//        long s = System.currentTimeMillis();
        getNewSizeView(newW, newH, true, ratio);//耗时6~73ms
//        Log.i(TAG, "use time:" + (System.currentTimeMillis() - s));
//        reDraw();
    }

    private int getDistance(Point p1, Point p2) {
        int distance = 0;// 两点之间的距离
        int sideH = 0, sideV = 0;// 垂直边和水平边
        if (p2.x > p1.x) {
            sideV = p2.x - p1.x;
        } else {
            sideV = p1.x - p2.x;
        }
        if (p2.y > p1.y) {
            sideH = p2.y - p1.y;
        } else {
            sideH = p1.y - p2.y;
        }
        distance = (int) Math.sqrt((sideV * sideV) + (sideH * sideH));// 开根号
        return distance;
    }

    ScaleGestureDetector.OnScaleGestureListener scaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            /*float preSpan = detector.getPreviousSpan();
            float curSpan = detector.getCurrentSpan();
//            Log.i(TAG, "onScale: diff=" + (preSpan - curSpan));
            float preScaleRatio = curScaleRatio;
            if (curSpan < preSpan) {
                // 缩小
                curScaleRatio = curScaleRatio - (preSpan - curSpan) / 2000;
                NumberFormat nf = new DecimalFormat("00.00000");
                curScaleRatio = Float.parseFloat(nf.format(curScaleRatio));
//                Log.i(TAG, "onScale: small: " + nf.format((preSpan - curSpan) / 2000));
            } else {
                // 放大
                curScaleRatio = curScaleRatio + (curSpan - preSpan) / 2000;
                NumberFormat nf = new DecimalFormat("00.00000");
                curScaleRatio = Float.parseFloat(nf.format(curScaleRatio));
//                Log.i(TAG, "onScale: large: " + nf.format((curSpan - preSpan) / 2000));
            }*/

            float scale = detector.getScaleFactor();
            Log.i(TAG, "onScale: scale:" + scale);

            if (Math.abs(1 - scale) < 0.001) return true;

            if (scale > 1) {//放大
                if (scale >= 1.05)
                    curScaleRatio *= (scale * 0.99);
                else curScaleRatio *= scale;
            } else if (scale < 1) {//缩小
                if (scale <= 0.95)
                    curScaleRatio *= (scale * 1.05);
                else curScaleRatio *= scale;
            } else return true;

//            Log.i(TAG, "onScale: curScale: " + curScaleRatio);
            if (curScaleRatio > maxScaleRatio) {// 返回true取消处理伸缩手势事件
                curScaleRatio = maxScaleRatio;
                return true;
            } else if (curScaleRatio < minScaleRatio) {
                curScaleRatio = minScaleRatio;
                return true;
            }

            //做缩放操作...
//            long s = System.currentTimeMillis();
            scaleView(curScaleRatio);
//            Log.i(TAG, "use time:" + (System.currentTimeMillis() - s));
            return true;//执行完缩放操作一定要返回true，否则下次回调的结果将以上次的结果为基准增减的
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            Log.d(TAG, "onScaleBegin: ");
            isZooming = true;
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            Log.d(TAG, "onScaleEnd: ");
            scaleView(curScaleRatio);
            reDraw();//将重绘放在这里进行，避免频繁重绘导致程序界面卡顿
//            isZooming = false;
        }
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction() & MotionEvent.ACTION_MASK;
        float px1 = event.getX();
        float py1 = event.getY();

        if (curScaleRatio > 1.0f || curScaleRatio < 1.0f) {//！！！触点也要缩放，否则画出来的线条是缩放后的位置而不是手指触摸的位置
            px1 = px1 / curScaleRatio;
            py1 = py1 / curScaleRatio;
//            Log.d(TAG, "onTouchEvent: ratio:" + curScaleRatio + " px1:" + px1 + " py1:" + py1);
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (isZooming) break;
//                Log.d(TAG, "onTouchEvent: down");
                switch (mode) {
                    case 0:
                        if (mBufferBitmap == null) {
                            initBuffer();
                        }
//                        mBufferCanvas.save();
//                        mBufferCanvas.scale(curScaleRatio, curScaleRatio);
                        mLastX = px1;
                        mLastY = py1;
                        if (mPath == null) {
                            mPath = new Path();
                        }
                        mPath.moveTo(px1, py1);
                        break;
                    case MODE_DRAG:
                        firstX = px1;
                        firstY = py1;
                        break;
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
//                Log.i(TAG, "onTouchEvent: pointer down");
                lastMode = mode;
//                mode = MODE_ZOOM;
                /*pointerCount = event.getPointerCount();
                if (pointerCount == 2) {
                    float x1 = event.getX();
                    float y1 = event.getY();
                    float x2 = event.getX(1);
                    float y2 = event.getY(1);
                    distance = getDistance(new Point((int) x1, (int) y1), new Point((int) x2, (int) y2));
                }*/
                break;
            case MotionEvent.ACTION_MOVE:
                if (isZooming) {
                    break;
                }
//                Log.i(TAG, "onTouchEvent: move");
                switch (mode) {
                    case 0:
                        //这里终点设为两点的中心点的目的在于使绘制的曲线更平滑，如果终点直接设置为x,y，效果和lineto是一样的,实际是折线效果
                        mPath.quadTo(mLastX, mLastY, (px1 + mLastX) / 2, (py1 + mLastY) / 2);
                        if (mMode == Mode.ERASER && !mCanEraser) {
                            break;
                        }
                        mBufferCanvas.drawPath(mPath, mPaint);
                        invalidate();
                        savePath = new Path(mPath);
                        mPath.moveTo((px1 + mLastX) / 2, (py1 + mLastY) / 2);
                        mLastX = px1;
                        mLastY = py1;
                        break;
                    case MODE_DRAG:
                        if (mBufferCanvas != null && mBufferBitmap != null) {
                            float x = px1 - firstX;
                            float y = py1 - firstY;
                            if (Math.abs(x) > 2)//缩放到最小会抖动，原因未知
                                setX(curX += x);
                            if (Math.abs(y) > 2)
                                setY(curY += y);
                        }
                        break;
                    /*case MODE_ZOOM:
//                        Log.i(TAG, "onTouchEvent: zoom");
                        if ((pointerCount = event.getPointerCount()) < 2) break;
                        float x1 = event.getX();
                        float y1 = event.getY();
                        float x2 = event.getX(1);
                        float y2 = event.getY(1);
                        int dis = getDistance(new Point((int) x1, (int) y1), new Point((int) x2, (int) y2));
                        int diffVal = dis - distance;
                        if (Math.abs(diffVal) > 15) {//阈值
                            float ratio = (float) diffVal / 10000f;//缩小每次缩放的比率，控制缩放速度
//                            Log.i(TAG, "scale ratio: " + ratio);
                            if (diffVal > 0) {//放大
                                if (curScaleRatio < maxScaleRatio && curScaleRatio + ratio <= maxScaleRatio) {
                                    scaleView(curScaleRatio + ratio);
                                }
                            } else if (diffVal < 0) {//缩小
                                if (curScaleRatio > minScaleRatio && curScaleRatio + ratio >= minScaleRatio) {
                                    scaleView(curScaleRatio + ratio);
                                }
                            }
                        }
                        break;*/
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
//                Log.i(TAG, "onTouchEvent: pointer up");
                pointerCount = event.getPointerCount();
                break;
            case MotionEvent.ACTION_UP:
                if (isZooming) {//!!!解决缩放时也会绘制线条的问题
                    isZooming = false;
                    mPath.reset();
                    savePath.reset();
                    break;
                }
//                Log.d(TAG, "onTouchEvent: up");
                switch (mode) {
                    case 0:
                        if (mMode == Mode.DRAW || mCanEraser) {
                            saveDrawingPath();
                            thumbnail = compressBitmap(mBufferBitmap, 0.3f);//保存一张缩略图
                        }
                        mPath.reset();
//                        if (mBufferCanvas.getSaveCount() > 0)
//                            mBufferCanvas.restore();
                        break;
                    case MODE_DRAG:
                        break;
                    /*case MODE_ZOOM:
                        mode = lastMode;
                        break;*/
                }
                break;
        }
        detector.onTouchEvent(event);
        return true;
    }

    public void drawBg() {
        if (srcBg != null) {
            mBufferCanvas.drawBitmap(srcBg, 0, 0, mPaint);
            invalidate();
        }
    }
}
